//! HTTP built-in plugin
//! 
//! This plugin provides HTTP/HTTPS request functionality,
//! corresponding to HttpRunner's HTTP capabilities.

use super::BuiltinPlugin;
use crate::error::{Error, Result, NetworkError};
use crate::models::{
    request::{HttpRequest, HttpResponse, HttpMethod},
    Value, ValueMap,
};
use reqwest::{Client, ClientBuilder};
use serde_json;
use std::collections::HashMap;
use std::time::Duration;

/// HTTP built-in plugin
#[derive(Debug)]
pub struct HttpPlugin {
    /// HTTP client
    client: Option<Client>,
    
    /// Plugin configuration
    config: ValueMap,
}

impl HttpPlugin {
    /// Create a new HTTP plugin
    pub fn new() -> Self {
        Self {
            client: None,
            config: HashMap::new(),
        }
    }
    
    /// Get or create HTTP client
    fn get_client(&self) -> Result<&Client> {
        self.client.as_ref().ok_or_else(|| {
            Error::Network(NetworkError::RequestFailed {
                url: "N/A".to_string(),
                status: 0,
            })
        })
    }
    
    /// Execute HTTP request
    async fn execute_request(&self, args: &[Value]) -> Result<Value> {
        if args.is_empty() {
            return Err(Error::Network(NetworkError::InvalidUrl {
                url: "Missing request configuration".to_string(),
            }));
        }
        
        // Parse request from arguments
        let request_config = &args[0];
        let http_request: HttpRequest = serde_json::from_value(request_config.clone())
            .map_err(|e| Error::Network(NetworkError::InvalidUrl {
                url: format!("Invalid request format: {}", e),
            }))?;
        
        let client = self.get_client()?;
        
        // Build reqwest request
        let mut req_builder = match http_request.method {
            HttpMethod::GET => client.get(&http_request.url),
            HttpMethod::POST => client.post(&http_request.url),
            HttpMethod::PUT => client.put(&http_request.url),
            HttpMethod::DELETE => client.delete(&http_request.url),
            HttpMethod::PATCH => client.patch(&http_request.url),
            HttpMethod::HEAD => client.head(&http_request.url),
            HttpMethod::OPTIONS => client.request(reqwest::Method::OPTIONS, &http_request.url),
            HttpMethod::TRACE => client.request(reqwest::Method::TRACE, &http_request.url),
            HttpMethod::CONNECT => client.request(reqwest::Method::CONNECT, &http_request.url),
        };
        
        // Add headers
        for (key, value) in &http_request.headers {
            req_builder = req_builder.header(key, value);
        }
        
        // Add query parameters
        if !http_request.params.is_empty() {
            let params: Vec<(String, String)> = http_request.params
                .iter()
                .map(|(k, v)| (k.clone(), v.to_string()))
                .collect();
            req_builder = req_builder.query(&params);
        }
        
        // Add body
        if let Some(ref json_body) = http_request.json {
            req_builder = req_builder.json(json_body);
        } else if let Some(ref body) = http_request.body {
            req_builder = req_builder.body(body.to_string());
        } else if let Some(ref form_data) = http_request.data {
            // Handle form data
            if let Some(obj) = form_data.as_object() {
                let form: HashMap<String, String> = obj
                    .iter()
                    .map(|(k, v)| (k.clone(), v.to_string()))
                    .collect();
                req_builder = req_builder.form(&form);
            }
        }
        
        // Set timeout
        if let Some(timeout) = http_request.timeout {
            req_builder = req_builder.timeout(Duration::from_secs_f64(timeout));
        }
        
        // Execute request
        let start_time = std::time::Instant::now();
        let response = req_builder.send().await
            .map_err(|e| Error::Network(NetworkError::RequestFailed {
                url: http_request.url.clone(),
                status: 0,
            }))?;
        
        let elapsed = start_time.elapsed();
        let status_code = response.status().as_u16();
        let headers: HashMap<String, String> = response.headers()
            .iter()
            .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
            .collect();
        
        // Extract content type and length
        let content_type = headers.get("content-type").cloned();
        let content_length = headers.get("content-length")
            .and_then(|v| v.parse::<u64>().ok());

        // Get response body as bytes first
        let response_bytes = response.bytes().await
            .map_err(|e| Error::Network(NetworkError::RequestFailed {
                url: http_request.url.clone(),
                status: status_code,
            }))?;

        // Convert to text
        let response_text = String::from_utf8_lossy(&response_bytes).to_string();

        // Try to parse as JSON
        let json_value = if content_type.as_ref()
            .map(|ct| ct.contains("application/json"))
            .unwrap_or(false) {
            serde_json::from_str::<Value>(&response_text).ok()
        } else {
            None
        };

        // Extract cookies (basic implementation)
        let cookies = headers.iter()
            .filter(|(k, _)| k.to_lowercase() == "set-cookie")
            .map(|(_, v)| {
                // Simple cookie parsing - just extract name=value
                if let Some(eq_pos) = v.find('=') {
                    let name = v[..eq_pos].trim().to_string();
                    let value = v[eq_pos + 1..].split(';').next().unwrap_or("").trim().to_string();
                    (name, value)
                } else {
                    ("unknown".to_string(), v.clone())
                }
            })
            .collect();

        // Create HTTP response
        let http_response = HttpResponse {
            status_code,
            reason: status_code.to_string(), // Use status code as reason for now
            headers,
            cookies,
            text: response_text,
            content: response_bytes.to_vec(),
            json: json_value,
            encoding: "utf-8".to_string(),
            url: http_request.url,
            elapsed,
            content_length,
            content_type,
            version: crate::models::request::HttpVersion::Http11, // Default to HTTP/1.1
            ok: (200..300).contains(&status_code),
            history: Vec::new(), // TODO: Handle redirects in future
        };
        
        // Convert to JSON value
        serde_json::to_value(http_response)
            .map_err(|e| Error::Network(NetworkError::RequestFailed {
                url: "response_serialization".to_string(),
                status: 0,
            }))
    }
    
    /// Get HTTP client info
    async fn get_client_info(&self, _args: &[Value]) -> Result<Value> {
        Ok(serde_json::json!({
            "name": "reqwest",
            "version": env!("CARGO_PKG_VERSION"),
            "features": ["json", "multipart", "stream", "cookies", "gzip"]
        }))
    }

    /// Validate HTTP request parameters
    async fn validate_request(&self, args: &[Value]) -> Result<Value> {
        if args.is_empty() {
            return Ok(serde_json::json!({
                "valid": false,
                "error": "Missing request configuration"
            }));
        }

        // Try to parse request
        match serde_json::from_value::<HttpRequest>(args[0].clone()) {
            Ok(request) => {
                // Basic validation
                if request.url.is_empty() {
                    return Ok(serde_json::json!({
                        "valid": false,
                        "error": "URL cannot be empty"
                    }));
                }

                if !request.url.starts_with("http://") && !request.url.starts_with("https://") {
                    return Ok(serde_json::json!({
                        "valid": false,
                        "error": "URL must start with http:// or https://"
                    }));
                }

                Ok(serde_json::json!({
                    "valid": true,
                    "method": request.method,
                    "url": request.url
                }))
            }
            Err(e) => Ok(serde_json::json!({
                "valid": false,
                "error": format!("Invalid request format: {}", e)
            }))
        }
    }

    /// Create a simple GET request
    async fn get(&self, args: &[Value]) -> Result<Value> {
        if args.is_empty() {
            return Err(Error::Network(NetworkError::InvalidUrl {
                url: "Missing URL".to_string(),
            }));
        }

        let url = args[0].as_str().ok_or_else(|| {
            Error::Network(NetworkError::InvalidUrl {
                url: "URL must be a string".to_string(),
            })
        })?;

        let mut request = HttpRequest::new(HttpMethod::GET, url.to_string());

        // Add optional headers from second argument
        if args.len() > 1 {
            if let Some(headers_obj) = args[1].as_object() {
                for (key, value) in headers_obj {
                    if let Some(value_str) = value.as_str() {
                        request.headers.insert(key.clone(), value_str.to_string());
                    }
                }
            }
        }

        self.execute_request(&[serde_json::to_value(request)?]).await
    }

    /// Create a simple POST request
    async fn post(&self, args: &[Value]) -> Result<Value> {
        if args.is_empty() {
            return Err(Error::Network(NetworkError::InvalidUrl {
                url: "Missing URL".to_string(),
            }));
        }

        let url = args[0].as_str().ok_or_else(|| {
            Error::Network(NetworkError::InvalidUrl {
                url: "URL must be a string".to_string(),
            })
        })?;

        let mut request = HttpRequest::new(HttpMethod::POST, url.to_string());

        // Add JSON body from second argument
        if args.len() > 1 {
            request.json = Some(args[1].clone());
        }

        // Add optional headers from third argument
        if args.len() > 2 {
            if let Some(headers_obj) = args[2].as_object() {
                for (key, value) in headers_obj {
                    if let Some(value_str) = value.as_str() {
                        request.headers.insert(key.clone(), value_str.to_string());
                    }
                }
            }
        }

        self.execute_request(&[serde_json::to_value(request)?]).await
    }
    
    /// Set HTTP client configuration
    async fn configure_client(&mut self, args: &[Value]) -> Result<Value> {
        if args.is_empty() {
            return Ok(serde_json::json!({"status": "no_config"}));
        }
        
        let config = &args[0];
        
        // Build new client with configuration
        let mut builder = ClientBuilder::new();
        
        if let Some(timeout) = config.get("timeout").and_then(|v| v.as_f64()) {
            builder = builder.timeout(Duration::from_secs_f64(timeout));
        }
        
        if let Some(verify) = config.get("verify").and_then(|v| v.as_bool()) {
            builder = builder.danger_accept_invalid_certs(!verify);
        }
        
        if let Some(user_agent) = config.get("user_agent").and_then(|v| v.as_str()) {
            builder = builder.user_agent(user_agent);
        }
        
        self.client = Some(builder.build().map_err(|e| {
            Error::Network(NetworkError::RequestFailed {
                url: "client_configuration".to_string(),
                status: 0,
            })
        })?);
        
        Ok(serde_json::json!({"status": "configured"}))
    }
}

impl BuiltinPlugin for HttpPlugin {
    fn name(&self) -> &str {
        "http"
    }
    
    fn version(&self) -> &str {
        "1.0.0"
    }
    
    fn functions(&self) -> Vec<String> {
        vec![
            "request".to_string(),
            "get".to_string(),
            "post".to_string(),
            "put".to_string(),
            "delete".to_string(),
            "patch".to_string(),
            "head".to_string(),
            "options".to_string(),
            "client_info".to_string(),
            "configure".to_string(),
            "validate_request".to_string(),
        ]
    }
    
    async fn call_function(&self, function_name: &str, args: &[Value]) -> Result<Value> {
        match function_name {
            "request" => self.execute_request(args).await,
            "get" => self.get(args).await,
            "post" => self.post(args).await,
            "validate_request" => self.validate_request(args).await,
            "client_info" => self.get_client_info(args).await,
            "put" | "delete" | "patch" | "head" | "options" => {
                // Convenience methods for other HTTP methods
                if args.is_empty() {
                    return Err(Error::Network(NetworkError::InvalidUrl {
                        url: "Missing URL".to_string(),
                    }));
                }

                let url = args[0].as_str().ok_or_else(|| {
                    Error::Network(NetworkError::InvalidUrl {
                        url: "Invalid URL format".to_string(),
                    })
                })?;

                let method = match function_name {
                    "put" => HttpMethod::PUT,
                    "delete" => HttpMethod::DELETE,
                    "patch" => HttpMethod::PATCH,
                    "head" => HttpMethod::HEAD,
                    "options" => HttpMethod::OPTIONS,
                    _ => HttpMethod::GET,
                };

                let mut request = HttpRequest::new(method, url.to_string());
                if args.len() > 1 {
                    request.json = Some(args[1].clone());
                }

                let request_value = serde_json::to_value(request)?;
                self.execute_request(&[request_value]).await
            }
            "client_info" => self.get_client_info(args).await,
            "configure" => {
                // This is a mutable operation, but we can't modify self here
                // In a real implementation, we'd need to handle this differently
                Ok(serde_json::json!({"status": "configure_not_supported_in_immutable_context"}))
            }
            _ => Err(Error::Plugin(crate::error::PluginError::FunctionNotFound {
                function_name: function_name.to_string(),
            })),
        }
    }
    
    async fn initialize(&mut self, config: ValueMap) -> Result<()> {
        self.config = config;
        
        // Create default HTTP client
        let mut builder = ClientBuilder::new();
        
        // Configure from config
        if let Some(timeout) = self.config.get("timeout").and_then(|v| v.as_f64()) {
            builder = builder.timeout(Duration::from_secs_f64(timeout));
        }
        
        if let Some(verify) = self.config.get("verify").and_then(|v| v.as_bool()) {
            builder = builder.danger_accept_invalid_certs(!verify);
        }
        
        self.client = Some(builder.build().map_err(|e| {
            Error::Network(NetworkError::RequestFailed {
                url: "client_initialization".to_string(),
                status: 0,
            })
        })?);
        
        tracing::info!("HTTP plugin initialized");
        Ok(())
    }
    
    async fn cleanup(&mut self) -> Result<()> {
        self.client = None;
        tracing::info!("HTTP plugin cleaned up");
        Ok(())
    }
}

impl Default for HttpPlugin {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_http_plugin_creation() {
        let plugin = HttpPlugin::new();
        assert_eq!(plugin.name(), "http");
        assert_eq!(plugin.version(), "1.0.0");
        assert!(plugin.functions().contains(&"request".to_string()));
    }

    #[tokio::test]
    async fn test_http_plugin_initialization() {
        let mut plugin = HttpPlugin::new();
        let config = HashMap::new();
        
        let result = plugin.initialize(config).await;
        assert!(result.is_ok());
        assert!(plugin.client.is_some());
    }
}
